package Math::Calculus::NewtonRaphson;

use strict;
use warnings;

use Moose;
use Math::Calculus::Expression;
use Math::Calculus::Differentiate;

our $VERSION = '0.01';

#TODO reclamar se a exp tiver mais de uma var (die)
has expression => ( isa => 'Str', is => 'rw', coerce => 1, required => 1 );

#TODO reclamar se a variável não corresponder à da exp (die)
has variable => ( isa => 'Str', is => 'rw', required => 1 );
has start    => ( isa => 'Num', is => 'rw', required => 1 );

#TODO receber dois valores de precisão: i. para f(x); ii. para o intervalo (next - prev)
has precision => ( isa => 'Int', is => 'rw', required => 1 );

#atualmente apenas para o valor de f(x)

has partial_results => ( is => 'rw', default => sub { [] } );

sub run {
    my $self = shift;
    my $exp  = Math::Calculus::Expression->new;
    $exp->addVariable( my $var = $self->variable );
    $exp->setExpression( $self->expression );
    my $diffExp = Math::Calculus::Differentiate->new;
    $diffExp->setExpression( $self->expression );
    $diffExp->addVariable($var);
    unless ( $diffExp->differentiate($var) ) {
        die 'Unable to differentiate expression';
    }
    my ( $x_prev, $e ) = ( $self->start, 10**-$self->precision );
    my $x_next = _xnext(
        $x_prev,
        my $fx_prev = $exp->evaluate( $var => $x_prev ),
        $diffExp->evaluate( $var => $x_prev )
    );
    my $fx_next = $exp->evaluate( $var => $x_next );
    push @{ $self->partial_results }, { x => $x_prev, y => $fx_prev };
    push @{ $self->partial_results }, { x => $x_next, y => $fx_next };
    while ( abs $fx_prev > $e && abs $fx_next > $e ) {
        $x_prev = $x_next;
        $x_next = _xnext(
            $x_prev,
            $fx_prev = $exp->evaluate( $var => $x_prev ),
            $diffExp->evaluate( $var => $x_prev )
        );
        $fx_next = $exp->evaluate( $var => $x_next );
        push @{ $self->partial_results }, { x => $x_prev, y => $fx_prev };
        push @{ $self->partial_results }, { x => $x_next, y => $fx_next };
    }
    if ( abs $fx_prev <= $e ) {
        return $x_prev;
    }
    return $x_next;
}

sub _xnext {
    my ( $x, $fx, $flx ) = @_;
    return $x - $fx / $flx;
}

1;
__END__

=head1 NAME

Math::Calculus::NewtonRaphson - Perl extension for blah blah blah

=head1 SYNOPSIS

  use Math::Calculus::NewtonRaphson;
  blah blah blah

=head1 DESCRIPTION

=head2 EXPORT

None by default.

=head1 SEE ALSO

Math::Calculus::Bisection

=head1 AUTHORS

Wallace Reis E<lt>wreis@cpan.orgE<gt>, Eden Cardim E<lt>edenc@cpan.orgE<gt>

=head1 COPYRIGHT AND LICENSE

Copyright (C) 2007 by Wallace Reis

This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself, either Perl version 5.8.8 or,
at your option, any later version of Perl 5 you may have available.


=cut
